package de.slothsoft.commons.swing.databinding;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPasswordField;
import javax.swing.text.JTextComponent;

import de.slothsoft.commons.DisplayUtil;
import de.slothsoft.commons.StringParser;
import de.slothsoft.commons.swing.JCheckList;
import de.slothsoft.commons.swing.KeyControl;

public final class DataBinding<T> {

	// FIXME: refactor data binding, using interfaces or something to hook custom controls

	private final List<Binding> bindings = new ArrayList<>();
	private T model;

	public DataBinding(T model) {
		this.model = model;
	}

	public Binding bind(JComponent component, T model, String property) {
		Binding binding = new Binding();
		binding.component = component;
		binding.property = property;
		this.bindings.add(binding);
		return binding;
	}

	public void updateTargets() throws DataBindingException {
		for (Binding binding : this.bindings) {
			updateTarget(binding);
		}
	}

	public void updateModels() throws DataBindingException {
		for (Binding binding : this.bindings) {
			updateModel(binding);
		}
	}

	public T getModel() {
		return this.model;
	}

	public void setModel(T model) {
		this.model = model;
	}

	// TODO: the following methods are ugly

	private void updateTarget(Binding binding) throws DataBindingException {
		if (binding.component instanceof JTextComponent) {
			((JTextComponent) binding.component).setText(DisplayUtil.toString(PropertyUtil.invokeGetter(this.model,
					binding.property)));
		} else if (binding.component instanceof JCheckBox) {
			((JCheckBox) binding.component).setSelected((Boolean) PropertyUtil.invokeGetter(this.model,
					binding.property));
		} else if (binding.component instanceof JComboBox) {
			((JComboBox<?>) binding.component).setSelectedItem(PropertyUtil.invokeGetter(this.model, binding.property));
		} else if (binding.component instanceof KeyControl) {
			((KeyControl) binding.component).setKey(((Integer) PropertyUtil.invokeGetter(this.model, binding.property))
					.intValue());
		} else if (binding.component instanceof JCheckList<?>) {
			((JCheckList) binding.component).setSelectedItems((Collection<?>) PropertyUtil.invokeGetter(this.model,
					binding.property));
		}
	}

	protected void updateModel(Binding binding) throws DataBindingException {
		if (binding.component instanceof JPasswordField) {
			updateModelPasswordField((JTextComponent) binding.component, binding);
		} else if (binding.component instanceof JTextComponent) {
			updateModelTextField((JTextComponent) binding.component, binding);
		} else if (binding.component instanceof JCheckBox) {
			updateModelCheckBox((JCheckBox) binding.component, binding);
		} else if (binding.component instanceof JComboBox) {
			updateModelComboBox((JComboBox<?>) binding.component, binding);
		} else if (binding.component instanceof KeyControl) {
			updateModelKeyControl((KeyControl) binding.component, binding);
		} else if (binding.component instanceof JCheckList<?>) {
			Collection<?> selected = ((JCheckList) binding.component).getSelectedItems((Collection<?>) PropertyUtil
					.invokeGetter(this.model, binding.property));
			Method getter = PropertyUtil.getGetter(this.model.getClass(), binding.property);
			PropertyUtil.invokeSetter(this.model, binding.property, selected, getter.getReturnType());
		}
	}

	private void updateModelTextField(JTextComponent text, Binding binding) {
		Method getter = PropertyUtil.getGetter(this.model.getClass(), binding.property);
		invokeSetter(text, binding, getter.getReturnType());
	}

	private void updateModelPasswordField(JTextComponent text, Binding binding) {
		invokeSetter(text, binding, char[].class);
	}

	private void invokeSetter(JTextComponent text, Binding binding, Class<?> returnType) {
		Object value = StringParser.parse(text.getText(), returnType);
		PropertyUtil.invokeSetter(this.model, binding.property, value, returnType);
	}

	private void updateModelCheckBox(JCheckBox component, Binding binding) {
		PropertyUtil.invokeSetter(this.model, binding.property, component.isSelected(), boolean.class);
	}

	private void updateModelComboBox(JComboBox<?> component, Binding binding) {
		Object selected = component.getSelectedItem();
		Method getter = PropertyUtil.getGetter(this.model.getClass(), binding.property);
		PropertyUtil.invokeSetter(this.model, binding.property, selected, getter.getReturnType());
	}

	private void updateModelKeyControl(KeyControl component, Binding binding) {
		Integer selectedKey = component.getKey();
		PropertyUtil.invokeSetter(this.model, binding.property, selectedKey, int.class);
	}

}
